import { __private, Asset, JsonAsset, assetManager, AssetManager } from "cc";
import { lerror } from "../logger/Logger";
import { Constructor } from "cc";

export type AssetBundle = AssetManager.Bundle;
export type AssetReqeustItem = AssetManager.RequestItem;
export type BundleLoadOption = {
    [k: string]: any;
    version?: string;
};

export type RemoteLoadOption = {
    [k: string]: any;
    ext?: string;
} | null;

export type JsonAssetSource = Record<string, any> | null;

export type BundleCallback = (bundle: AssetBundle) => void;
export type BundlesCallback = (bundle: AssetBundle[]) => void;

export type BundleLoadedCallback = (err: Error, bundle: AssetBundle) => void;
export type BundlesLoadedCallback = (err: Error, bundles: AssetBundle[]) => void;
export type ErrorCallback = (err: Error) => void;

export type AssetType<T extends Asset> = Constructor<T>;
export type AssetLoadProgressCallback = (finished: number, total: number, item: AssetReqeustItem) => void;
export type AssetLoadedCallback<T> = (err: Error | null, asset: T) => void;

export default class ResLoader {
    public static LoadBundle(nameOrUrl: string, onComplete?: BundleLoadedCallback): void;
    public static LoadBundle(nameOrUrl: string, options?: BundleLoadOption, onComplete?: BundleLoadedCallback): void;
    public static LoadBundle(nameOrUrl: string, options: BundleLoadOption | BundleLoadedCallback, onComplete?: BundleLoadedCallback): void {
        assetManager.loadBundle(nameOrUrl, options, onComplete);
    }

    /**
     * 加载多个资源包
     * @param nameOrUrls 资源包名称数组
     * @param onComplete 所有资源包加载完成回调
     * @param onCompleteOne 每个资源包加载回调
     * @param onError 资源包加载失败回调
     */
    public static LoadBundles(nameOrUrls: string[], onComplete?: BundlesCallback): void;
    public static LoadBundles(nameOrUrls: string[], onComplete?: BundlesCallback, onCompleteOne?: BundleCallback): void;
    public static LoadBundles(nameOrUrls: string[], onComplete?: BundlesCallback, onCompleteOne?: BundleCallback, onError?: ErrorCallback): void;
    public static LoadBundles(nameOrUrls: string[], options?: BundleLoadOption, onComplete?: BundlesCallback): void;
    public static LoadBundles(nameOrUrls: string[], options?: BundleLoadOption, onComplete?: BundlesCallback, onCompleteOne?: BundleCallback): void;
    public static LoadBundles(nameOrUrls: string[], options?: BundleLoadOption, onComplete?: BundlesCallback, onCompleteOne?: BundleCallback, onError?: ErrorCallback): void;
    public static LoadBundles(nameOrUrls: string[],
        options?: BundleLoadOption | BundlesCallback,
        onComplete?: BundlesCallback | BundleCallback,
        onCompleteOne?: BundleCallback | ErrorCallback,
        onError?: ErrorCallback
    ): void {
        if (typeof options === "function") {
            onError = onCompleteOne as ErrorCallback;
            onCompleteOne = onComplete as BundleCallback;
            onComplete = options as BundlesCallback;
            options = null;
        }

        let promises: Promise<AssetBundle>[] = [];
        for (let bundleName of nameOrUrls) {
            promises.push(new Promise<AssetBundle>((resolve, reject) => {
                this.LoadBundle(bundleName, options, (err: Error, bundle: AssetBundle) => {
                    if (err) {
                        return reject(err);
                    }

                    if (onCompleteOne) {
                        (onCompleteOne as BundleCallback)(bundle);
                    }
                    resolve(bundle);
                })
            }));
        }

        Promise.all(promises)
            .then(onComplete as BundlesCallback)
            .catch(onError);
    }

    /**
     * 同步加载资源包
     * @param nameOrUrl 资源包名或url
     * @returns Promise
     */
    public static LoadBundleSync(nameOrUrl: string, options?: BundleLoadOption): Promise<AssetBundle> {
        return new Promise<AssetBundle>((resolve, reject) => {
            this.LoadBundle(nameOrUrl, options, (err: Error, bundle: AssetBundle) => {
                if (err) {
                    return reject(err);
                }

                return resolve(bundle);
            });
        });
    }

    public static LoadBundlesSync(nameOrUrls: string[], options: BundleLoadOption): Promise<AssetBundle[]> {
        let promises: Promise<AssetBundle>[] = [];
        for (let index = 0; index < nameOrUrls.length; index++) {
            const bundleName = nameOrUrls[index];
            promises.push(this.LoadBundleSync(bundleName, options));
        }

        return Promise.all(promises);
    }

    /**
     * 加载远程资源
     * @param url 资源地址
     * @param options 加载选项
     * @param onComplete 加载完成回调
     */
    public static LoadRemote<T extends Asset>(url: string, onComplete?: AssetLoadedCallback<T>): void;
    public static LoadRemote<T extends Asset>(url: string, options: RemoteLoadOption, onComplete?: AssetLoadedCallback<T>): void;
    public static LoadRemote<T extends Asset>(url: string, options?: RemoteLoadOption | AssetLoadedCallback<T>, onComplete?: AssetLoadedCallback<T>): void {
        assetManager.loadRemote(url, options, onComplete);
    }

    /** 同步加载远程资源 */
    public static LoadRemoteSync<T extends Asset>(url: string, options?: RemoteLoadOption): Promise<T> {
        return new Promise<T>((resolve, reject) => {
            this.LoadRemote(url, options, (err: Error, asset: T) => {
                if (err) {
                    return reject(err);
                }

                return resolve(asset);
            });
        })
    }

    public static LoadRemoteJson(jsonUrl: string, options?: RemoteLoadOption, onComplete?: (err: Error, data: JsonAssetSource) => void) {
        if (!jsonUrl.endsWith(".json")) {
            jsonUrl += ".json";
        }

        if (options.ext !== undefined) {
            delete options.ext;
        }

        assetManager.loadRemote(jsonUrl, options, (err: Error, asset: JsonAsset) => {
            onComplete(err, asset.json);
        });
    }

    public static LoadRemoteJsonSync(jsonUrl: string): Promise<JsonAssetSource> {
        return new Promise((resolve, reject) => {
            this.LoadRemoteJson(jsonUrl, (err: Error, data: JsonAssetSource) => {
                if (err) {
                    return reject(err);
                }

                return resolve(data);
            });
        })
    }

    public static LoadAsset<T extends Asset>(assetPath: string, onProgress: AssetLoadProgressCallback, onComplete?: AssetLoadedCallback<T>): void;
    public static LoadAsset<T extends Asset>(assetPath: string, onComplete?: AssetLoadedCallback<T>): void;
    public static LoadAsset<T extends Asset>(assetPath: string, type: AssetType<T>, onComplete?: AssetLoadedCallback<T>): void;
    /**
     * 加载资源的敏捷方法
     * @param assetPath 资源路径(${bundlePath}/${assetPathInBundle})
     * @param type 资源类型
     * @param onComplete 加载完成回调
     * @returns void
     */
    public static LoadAsset<T extends Asset>(assetPath: string, type?, onComplete?): void {
        let [bundleName, pathInBundle] = this.SplitAssetPath(assetPath);
        if (!bundleName || !pathInBundle) {
            return;
        }

        let loadedBundle = assetManager.getBundle(bundleName);
        if (loadedBundle == null) {
            // AB包没有加载则先加载AB包
            this.LoadBundle(bundleName, (err: Error, bundle: AssetBundle) => {
                if (err) {
                    lerror(`加载资源:${assetPath}失败，AB包: ${bundleName} 加载失败`);
                    return;
                }

                bundle.load(pathInBundle, type, onComplete);
            })
            return;
        }

        loadedBundle.load(pathInBundle, type, onComplete);
    }

    public static LoadAssetSync<T extends Asset>(assetPath: string, type?: AssetType<T>): Promise<T> {
        return new Promise((resolve, rejvect) => {
            this.LoadAsset(assetPath, type, (err: Error, asset: T) => {
                if (err) {
                    return rejvect(err);
                }

                return resolve(asset);
            });
        });
    }

    public static LoadAssetsSync(assetPaths: string): Promise<Asset[]> {
        let promises: Promise<Asset>[] = [];
        for (let index = 0; index < assetPaths.length; index++) {
            const assetPath = assetPaths[index];
            promises.push(this.LoadAssetSync(assetPath));
        }

        return Promise.all(promises);
    }

    // 将资源路径分隔成 bundleName 和 pathInBundle
    private static SplitAssetPath(assetPath: string) {
        let separate = assetPath.indexOf('/');
        if (separate == -1) {
            return [null, null];
        }

        let bundleName = assetPath.substring(0, separate);
        let pathInBundle = assetPath.substring(separate + 1);
        return [bundleName, pathInBundle];
    }

    // 将 bundleName 和 pathInBundle 组合成资源路径
    public static AssetPath(bundleName: string, pathInBundle: string) {
        return `${bundleName}/${pathInBundle}`;
    }

    public static GetAsset<T extends Asset>(assetPath: string, type?: AssetType<T>) {
        let [bundleName, pathInBundle] = this.SplitAssetPath(assetPath);
        if (!bundleName || !pathInBundle) {
            lerror(`加载资源:${assetPath}失败,没有找到AB包分隔符`);
            return;
        }

        const bundle = assetManager.getBundle(bundleName);
        if (bundle == null) {
            lerror(`bundle: ${bundleName} has not load`);
            return;
        }

        return bundle.get(pathInBundle, type);
    }
}